#pragma once
#include <iostream>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"

enum{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};

const static int BACKLOG = 10;

class Socket{
public:
    void creSocket(){
        _sockFd = socket(AF_INET, SOCK_STREAM, 0);
        if(_sockFd < 0){
            ls(Fatal, "socket error, code: %d, error string: %s", errno, strerror(errno));
            exit(SOCKET_ERROR);
        }
    }

    void Bind(uint16_t port){
        sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if(bind(_sockFd, (sockaddr*)&local, sizeof(local)) < 0){
            ls(Fatal, "bind error, code: %d, error string: %s", errno, strerror(errno));
            exit(BIND_ERROR);
        }
    }

    void Listen(){
        if(listen(_sockFd, BACKLOG) < 0){
            ls(Fatal,"listen error, code: %d, error string: %s", errno, strerror(errno));
            exit(LISTEN_ERROR);
        }
    }

    int Accept(string* ip, uint16_t* port){
        sockaddr_in client;
        socklen_t len = sizeof(client);
        int fd = accept(_sockFd, (sockaddr*)&client, &len);
        if(fd < 0){
            ls(Warning,"accept error, code: %d, error string: %s", errno, strerror(errno));
            return -1;
        }

        char ipstr[64];
        inet_ntop(AF_INET, &client.sin_addr, ipstr, sizeof(ipstr));
        *ip = ipstr;
        *port = ntohs(client.sin_port);
        return fd;
    }

    bool Connect(const string& ip, uint16_t port){
        sockaddr_in client;
        memset(&client, 0, sizeof(client));
        client.sin_family = AF_INET;
        inet_pton(AF_INET, ip.c_str(), &(client.sin_addr));
        client.sin_port = htons(port);

        socklen_t len = sizeof(client);
        if(connect(_sockFd, (sockaddr*)&client, len) < 0){
            ls(Warning, "Connect error, code: %d, error string: %s", errno, strerror(errno));
            return false;
        }
        return true;
    }

    int getFD(){
        return _sockFd;
    }

    void Close(){
        close(_sockFd);
    }


private:
    int _sockFd;
};